package com.liuyi.netty.im.test.lychat.util;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.StrUtil;
import com.github.jedis.lock.JedisLock;
import com.liuyi.rapid.develop.tool.kit.ToolKit;
import com.liuyi.rapid.develop.tool.unusual.BizEnum;
import com.liuyi.rapid.develop.tool.unusual.BizException;
import lombok.extern.slf4j.Slf4j;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.function.Consumer;
import java.util.function.Function;

/**
 * JedisPool redis 工具类
 */
@Slf4j
public class JedisPoolUtil {

    private static JedisPool jedisPool = null;

    public static <T> T jedisFun(Function<Jedis, T> jedisTFunction) {
        try (final Jedis jedis = getInstance().getResource()) {
            return jedisTFunction.apply(jedis);
        }
    }

    /**
     * 自动关闭的Jedis
     */
    public static void jedis(Consumer<Jedis> jedisConsumer) {
        try (final Jedis jedis = getInstance().getResource()) {
            jedisConsumer.accept(jedis);
        }
    }

    /**
     * 这个获取的Jedis对象，每次使用必须要关闭，否则就会造成链接堵塞
     */
    private static JedisPool getInstance() {
        if (jedisPool == null) {
            synchronized (JedisPoolUtil.class) {
                jedisPool = new JedisPool();
            }
        }
        return jedisPool;
    }

    public static String serialNumber(String domain, int length) {
        return serialNumber(domain, null, length);
    }

    /**
     * 序列号生成，例如：SN20210201XXXXX  X为数字从0开始根据length长度生成，不足位置补0
     *
     * @param domain 生成的域，不同的域会相互隔离
     * @param preStr 生成的前缀名，可以为空
     * @param length 生成的长度，如1，最多生成10个，10最多生成100个，从0开始生成
     */
    public static String serialNumber(String domain, String preStr, int length) {
        preStr = preStr == null ? "" : preStr;
        try (Jedis jedis = getInstance().getResource();) {
            Long incr = jedis.incr(domain);
            if (incr == 1) {
                //如果key不存在，设置该key过期时间为当天
                long minutes = LocalDateTimeUtil.between(LocalDateTime.now(), ToolKit.getStartOrEndDayOfDay(LocalDate.now(), false)).toMinutes();
                jedis.expire(domain, Convert.toInt(minutes * 60));
            }
            //如果生产的数字大于length的长度则抛出异常，说数字生产过高
            if (incr.toString().length() > length) {
                throw new BizException("redis maximum specified generated " + length + " digits current gasReached highest");
            }
            return preStr + DateUtil.format(new Date(), "yyyyMMdd") + StrUtil.padPre(incr.toString(), length, "0");
        }
    }

    public static <T> T lockResults(int times, LockRessult<T> exec, String... keys) {
        return jedisFun(jedis -> lockResults(jedis, times, exec, keys));
    }

    public static <T> T lockResults(Jedis jedis, int times, LockRessult<T> exec, String... keys) {
        JedisLock[] locks = new JedisLock[keys.length];
        try {
            int i = 0;
            for (String key : keys) {
                locks[i] = new JedisLock(jedis, key, 1000, times);
                if (!locks[i].acquire()) {
                    throw new BizException(BizEnum.OPERATION_IS_BUSY);
                }
                i++;
            }
            return exec.exec();
        } catch (InterruptedException e) {
            log.error(e.getMessage(), e);
            throw new BizException(e.getMessage());
        } finally {
            for (JedisLock lock : locks) {
                lock.release();
            }
        }
    }

    public static void locks(int times, Lock exec, String... keys) {
        jedis(jedis -> {
            locks(jedis, times, exec, keys);
        });
    }

    /**
     * 锁定多个key
     */
    public static void locks(Jedis jedis, int times, Lock exec, String... keys) {
        JedisLock[] locks = new JedisLock[keys.length];
        try {
            int i = 0;
            for (String key : keys) {
                locks[i] = new JedisLock(jedis, key, 1000, times);
                if (!locks[i].acquire()) {
                    throw new BizException(BizEnum.OPERATION_IS_BUSY);
                }
                i++;
            }
            exec.exec();
        } catch (InterruptedException e) {
            log.error(e.getMessage(), e);
            throw new BizException(e.getMessage());
        } finally {
            for (JedisLock lock : locks) {
                lock.release();
            }
        }
    }

    public static <T> T lockResult(String key, int times, LockRessult<T> exec) {
        return jedisFun(jedis -> lockResult(jedis, key, times, exec));
    }

    public static <T> T lockResult(Jedis jedis, String key, int times, LockRessult<T> exec) {
        JedisLock jedisLock = new JedisLock(jedis, key, 1000, times);
        try {
            boolean acquire = jedisLock.acquire();
            if (acquire) {
                return exec.exec();
            } else {
                //获取锁失败，报异常
                throw new BizException(BizEnum.OPERATION_IS_BUSY);
            }
        } catch (InterruptedException e) {
            log.error(e.getMessage(), e);
            throw new BizException(e.getMessage());
        } finally {
            jedisLock.release();
        }
    }

    /**
     * 锁定单个key
     */
    public static void lock(String key, int times, Lock exec) {
        jedis(jedis -> lock(jedis, key, times, exec));
    }

    public static void lock(Jedis jedis, String key, int times, Lock exec) {
        JedisLock jedisLock = new JedisLock(jedis, key, 1000, times);
        try {
            boolean acquire = jedisLock.acquire();
            if (acquire) {
                exec.exec();
            } else {
                //获取锁失败，报异常
                throw new BizException(BizEnum.OPERATION_IS_BUSY);
            }
        } catch (InterruptedException e) {
            log.error(e.getMessage(), e);
            throw new BizException(e.getMessage());
        } finally {
            jedisLock.release();
        }
    }

    public interface Lock {

        /**
         * 获取锁成功执行
         */
        void exec();

    }

    public interface LockRessult<T> {

        /**
         * 获取锁成功执行
         */
        T exec();

    }
}
